﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using Krile.Kernel.Data;
using Krile.Kernel.Control;
using Krile.Kernel.Bridges;
using Krile.Kernel.WindowsEx;

namespace Krile.Forms.MainForm.Controls.InputMethodManager
{
    public partial class InputMethodManager : UserControl, IInputControlManager
    {

        ulong _inReplyToUID = 0;
        public ulong InReplyToUID
        {
            get { return _inReplyToUID; }
            private set
            {
                _inReplyToUID = value;
                ReplyInfoBar.Visible = _inReplyToUID != 0;
                ServiceLabel.Enabled = _inReplyToUID == 0;
                if (ReplyInfoBar.Visible)
                    ReplyInfoBar.Refresh();
                UpdateServiceSelText();
            }
        }

        public bool Independent = false;

        //save state
        /// <summary>
        /// Set in reply to uid
        /// </summary>
        /// <remarks>
        /// If you set zero to uid, it calls ClearReplyTo() method.
        /// </remarks>
        /// <param name="uid">Message uid</param>
        public void SetReplyTo(ulong uid)
        {
            if (Independent)
                throw new NotSupportedException();
            if (uid == 0)
            {
                ClearReplyTo();
                return;
            }

            if (!Core.StatusManager.IdExists(uid))
                throw new ArgumentOutOfRangeException("uid", "UID " + uid.ToString() + " isn't exists in Krile status manager.");
            SelectedServices = new Service[] { Core.StatusManager.GetStatus(uid).ParentService };
            if (curInputMethod.OnReplyRequired(uid))
                InReplyToUID = uid;
            if (OnShowRequired != null)
                OnShowRequired.Invoke(this, new EventArgs());
        }

        public ulong GetReplyTo()
        {
            return InReplyToUID;
        }

        /// <summary>
        /// Set service by enforced(replying will cleared)<para />
        /// This service change is temporally.<para />
        /// If you need set services with statically, you must use SetSelectedService() alternate this.
        /// </summary>
        /// <param name="svcs">services</param>
        public void SetServices(Service[] svcs, bool saveToTab)
        {
            InReplyToUID = 0;
            SelectedServices = svcs;
            if (!Independent && OnShowRequired != null)
                OnShowRequired.Invoke(this, new EventArgs());
            if (saveToTab && OnSelectedServicesChanged != null)
                OnSelectedServicesChanged.Invoke(this, new EventArgs());
        }

        /// <summary>
        /// initialize replying state
        /// </summary>
        public void ClearReplyTo()
        {
            if (!Independent &&
                InReplyToUID != 0 &&
                Core.FormManager.MainForm.PreviousForegroundForm != null &&
                Core.FormManager.MainForm.PreviousForegroundForm.TabControl.SelectedItem != null &&
                Core.FormManager.MainForm.PreviousForegroundForm.TabControl.SelectedItem is XTab.XTabTLItem)
            {
                var svcs = from id in ((XTab.XTabTLItem)Core.FormManager.MainForm.PreviousForegroundForm.TabControl.SelectedItem).TabData.LinkedServiceIds
                           where Core.PluginManager.LookupService(id) != null
                           select Core.PluginManager.LookupService(id);
                this.SelectedServices = svcs.ToArray<Krile.Kernel.Data.Service>();
            }
            InReplyToUID = 0;
        }

        /// <summary>
        /// Examine current input method's type
        /// </summary>
        /// <param name="type">test type</param>
        /// <returns>equals=true</returns>
        public bool CheckCurInputMethodIs(Type type)
        {
            return type.Equals(curInputMethod);
        }

        /// <summary>
        /// Get current input method's id.
        /// </summary>
        /// <param name="id">examine id</param>
        /// <remarks>
        /// If I haven't any method now, I always return false.
        /// </remarks>
        public bool CheckCurInputMethodIs(string id)
        {
            if (curInputMethod == null)
                return false;
            else
                return curInputMethod.IdentifyString == id;
        }

        /// <summary>
        /// Check existing current input method
        /// </summary>
        /// <returns></returns>
        public bool IsCurInputMethodExisted()
        {
            return curInputMethod != null;
        }

        /// <summary>
        /// Set focus to inputter
        /// </summary>
        public void FocusToInput()
        {
            if (curInputMethod != null)
                curInputMethod.FocusToInput();
        }

        /// <summary>
        /// Overwrite input text
        /// </summary>
        /// <param name="text">set text</param>
        public void SetInputText(string text)
        {
            SetInputText(text, false);
        }

        /// <summary>
        /// Overwrite input text
        /// </summary>
        /// <param name="text">set text</param>
        /// <param name="raiseInvalidOperationException">if internal input method reference is null, I raise it.</param>
        public void SetInputText(string text, bool raiseInvalidOperationException)
        {
            if (curInputMethod == null)
            {
                if (raiseInvalidOperationException)
                    throw new InvalidOperationException("Current input method is null.");
                else
                    return;
            }
            curInputMethod.InputText = text;
        }

        /// <summary>
        /// Get current inputted text
        /// </summary>
        /// <returns></returns>
        public string GetInputText()
        {
            if (curInputMethod == null)
                return null;
            else
                return curInputMethod.InputText;
        }

        /// <summary>
        /// Set extra data to current input method
        /// </summary>
        /// <remarks>
        /// For security reason, I set this prop only when id matches current method's id.
        /// You should examine current id before call this method.
        /// </remarks>
        /// <param name="id"></param>
        /// <param name="extraData"></param>
        public void SetExtraData(string id, object extraData)
        {
            if (curInputMethod == null)
                throw new InvalidOperationException("Current input method is null.");
            if (curInputMethod.IdentifyString != id)
                throw new ArgumentException("Id isn't match.Krile can't execute this method.", id);
            curInputMethod.ExtraData = extraData;
        }

        protected override void OnEnabledChanged(EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<EventArgs>(OnEnabledChanged), e);
            }
            else
            {
                this.UpdateServiceSelText();
                base.OnEnabledChanged(e);
            }
        }

        private IInputMethod curInputMethod = null;

        public bool ShowChangeVLocButton
        {
            get { return ChangeVLoc.Visible; }
            set { ChangeVLoc.Visible = value; }
        }

        public event EventHandler OnCloseRequired;

        public event EventHandler OnShowRequired;

        public event EventHandler OnChangeVLocRequired;

        public event EventHandler OnSelectedServicesChanged;

        private Service[] _selectedServices;
        public Service[] SelectedServices
        {
            get
            {
                return _selectedServices;
            }
            set
            {
                _selectedServices = value;
                UpdateServiceSelText();
                UpdateCurrentControl();
            }
        }

        public InputMethodManager()
        {
            InitializeComponent();
            _selectedServices = null;
            CloseButton.FlatAppearance.MouseOverBackColor = Color.FromArgb(150, Color.Pink);
            CloseButton.FlatAppearance.MouseDownBackColor = Color.FromArgb(255, Color.Pink);
            CancelReply.FlatAppearance.MouseOverBackColor = Color.FromArgb(90, Color.Red);
            CancelReply.FlatAppearance.MouseDownBackColor = Color.FromArgb(180, Color.Red);
            ChangeVLoc.FlatAppearance.MouseOverBackColor = Color.FromArgb(150, Color.SkyBlue);
            ChangeVLoc.FlatAppearance.MouseDownBackColor = Color.FromArgb(255, Color.SkyBlue);
        }

        public void Init()
        {
            DisposeCurInputMethod();
        }

        private void TitlebarPanel_Paint(object sender, PaintEventArgs e)
        {
            if (TitlebarPanel.Width > 0 && TitlebarPanel.Height > 0)
                using (var lgb = new LinearGradientBrush(TitlebarPanel.ClientRectangle, Color.RoyalBlue, Color.CornflowerBlue, 90f))
                    e.Graphics.FillRectangle(lgb, TitlebarPanel.ClientRectangle);
        }

        private void ServiceLabel_Resize(object sender, EventArgs e)
        {
            ServicePanel.Width = ServiceLabel.Width;
        }

        Dialogs.Editor.SelectService svcSelector = null;
        private void ServiceLabel_Click(object sender, EventArgs e)
        {
            ShowServiceSelector();
        }

        public void ShowServiceSelector()
        {
            if (!ServiceLabel.Enabled || (!Independent && !Core.FormManager.MainForm.GetPostboxState())) return;
            ServicePanel.BackColor = Color.CornflowerBlue;
            ServicePanel.BorderStyle = BorderStyle.FixedSingle;
            svcSelector = new Krile.Forms.Dialogs.Editor.SelectService();
            svcSelector.FormClosed += new FormClosedEventHandler(svcSelector_FormClosed);
            if (_selectedServices != null)
                svcSelector.SelectedServices = _selectedServices;
            svcSelector.Location = ServicePanel.PointToScreen(new Point(-1, ServicePanel.Height - 1));
            this.ParentForm.AddOwnedForm(svcSelector);
            svcSelector.Show();
        }

        void svcSelector_FormClosed(object sender, FormClosedEventArgs e)
        {
            SelectedServices = svcSelector.SelectedServices;
            if (OnSelectedServicesChanged != null)
                OnSelectedServicesChanged.Invoke(this, new EventArgs());
            svcSelector.Dispose();
            ServicePanel.BorderStyle = BorderStyle.None;
            ServicePanel.BackColor = Color.Transparent;
        }

        void UpdateServiceSelText()
        {
            this.SafeOperate(() =>
            {
                if (SelectedServices == null || SelectedServices.Length == 0)
                    ServiceLabel.Text = "Unselected";
                else if (SelectedServices.Length == 1)
                    ServiceLabel.Text = SelectedServices[0].Name;
                else if (SelectedServices.Length == Core.PluginManager.GetServiceEnumerator().Count<Service>())
                    ServiceLabel.Text = "All";
                else
                {
                    var snms = from s in SelectedServices
                               select s.Name;
                    ServiceLabel.Text = "Partial(" + String.Join(", ", snms.ToArray<string>()) + ")";

                }

                if (!ServiceLabel.Enabled && Parent.Enabled)
                    ServiceLabel.Text += " [LOCKED]";
                else
                    ServiceLabel.Text += "...";
            });
        }

        void UpdateCurrentControl()
        {
            this.SafeOperate(() =>
            {
                try
                {
                    string origText = String.Empty;
                    if (curInputMethod != null)
                        origText = curInputMethod.InputText;
                    this.Enabled = false;
                    if (SelectedServices == null || SelectedServices.Length == 0)
                    {
                        if (curInputMethod == null) return;
                        DisposeCurInputMethod();
                        return;
                    }
                    else if (SelectedServices.Length == 1)
                    {
                        if (curInputMethod != null && curInputMethod.IdentifyString == SelectedServices[0].ParentAgent.InputMethodId) return;
                        DisposeCurInputMethod();
                        if (SelectedServices[0].ParentAgent.InputMethodId == null)
                            curInputMethod = new DefaultInput();
                        else
                            curInputMethod = Core.ControlManager.LookupMethod(SelectedServices[0].ParentAgent.InputMethodId);
                        curInputMethod.ParentICM = this;
                    }
                    else
                    {
                        if (curInputMethod != null && curInputMethod.IdentifyString == null) return;
                        DisposeCurInputMethod();
                        curInputMethod = new DefaultInput() { ParentICM = this };
                    }
                    InitCurInputMethod();
                    if (!String.IsNullOrEmpty(origText))
                        curInputMethod.InputText = origText;
                }
                finally
                {
                    this.Enabled = true;
                }
            });
        }

        private void DisposeCurInputMethod()
        {
            if (curInputMethod == null) return;
            curInputMethod.Clear();
            ((UserControl)curInputMethod).Dispose();
            curInputMethod = null;
            UnselectedWarning.Visible = true;
        }

        private void InitCurInputMethod()
        {
            UnselectedWarning.Visible = false;
            Subsystem.Debugger.Assert(curInputMethod is UserControl, "INPUT METHOD can't cast to UserControl");
            curInputMethod.PostCallback = new MethodInvoker(PostCallbackMethod);
            curInputMethod.CancelCallback = new MethodInvoker(CancelCallbackMethod);
            ((UserControl)curInputMethod).Parent = MainPanel;
            ((UserControl)curInputMethod).Dock = DockStyle.Fill;
        }

        /// <summary>
        /// callback method after posted
        /// </summary>
        private void PostCallbackMethod()
        {
            this.Enabled = false;
            PostInvokerDelg pinv = new PostInvokerDelg(PostInvoker);
            pinv.BeginInvoke(curInputMethod.InputText, InReplyToUID, curInputMethod.ExtraData,
                (iar) =>
                {
                    var ret = ((PostInvokerDelg)iar.AsyncState).EndInvoke(iar);
                    this.SafeOperate<Service[]>(AfterPost, ret);
                }, pinv);
        }

        private void CancelCallbackMethod()
        {
            this.Clear();
            this.ClearReplyTo();
            if (Core.Config.Behavior.ClosePostboxAfterPost)
                this.CloseButton_Click(null, null);
        }

        delegate Service[] PostInvokerDelg(string text, ulong replyTo, object affix);
        private Service[] PostInvoker(String text, ulong replyTo, object affix)
        {
            List<Service> failedServices = new List<Service>();
            if (SelectedServices != null)
            {
                var sels = SelectedServices.Clone() as Service[];
                foreach (var s in sels)
                {
                    if (s.ParentAgent.Status.Updatable)
                    {
                        this.Enabled = false;
                        try
                        {
                            var r = s.ParentAgent.Status.Update(text, replyTo, affix);
                            if (!r.Succeeded)
                            {
                                Subsystem.Debugger.AddReport("Status update failed:" + s.Name + " - " + r.Message);
                                failedServices.Add(s);
                            }
                        }
                        catch (Exception e)
                        {
                            Subsystem.Debugger.AddReport(e);
                            failedServices.Add(s);
                        }
                    }
                }
            }
            return failedServices.ToArray();
        }

        private void AfterPost(Service[] rets)
        {
            this.Enabled = true;
            if (rets == null || rets.Length == 0)
            {
                this.Clear();
                this.ClearReplyTo();
                if (Core.Config.Behavior.ClosePostboxAfterPost)
                    this.CloseButton_Click(null, null);
            }
            else
            {
                var strs = from s in rets
                           select s.Name;
                var msgr = MessageBoxEx.Show(
                    String.Format(Lang.Msg.Update_Failed_Context, String.Join(Environment.NewLine, strs.ToArray())),
                    Lang.Msg.Update_Failed, Lang.Msg.Update_Failed_Caption, MessageBoxEx.MessageBoxExButtons.YesNoCancel,
                    MessageBoxEx.MessageBoxExIcon.Error);
                switch (msgr)
                {
                    case MessageBoxEx.DialogResultEx.Yes:
                        this.SelectedServices = rets;
                        break;
                    case MessageBoxEx.DialogResultEx.No:
                        this.Clear();
                        this.ClearReplyTo();
                        if (Core.Config.Behavior.ClosePostboxAfterPost)
                            this.CloseButton_Click(null, null);
                        break;
                    default:
                        break;
                }
            }
        }

        private void ReplyTextRenderer_Paint(object sender, PaintEventArgs e)
        {
            if (InReplyToUID != 0)
            {
                StringBuilder repDTB = new StringBuilder();
                if (Core.StatusManager.IdExists(InReplyToUID))
                {
                    var s = Core.StatusManager.GetStatus(InReplyToUID);
                    if (s.User.Name != null)
                    {
                        repDTB.Append(s.User.Name + ":");
                        if (Core.Config.Appearance.Renderer.ShowUserId)
                            repDTB.Append("(@" + s.User.Id + "):");
                    }
                    else
                        repDTB.Append("@" + s.User.Id + ":");
                    if (String.IsNullOrEmpty(s.Text.Original))
                        repDTB.Append("(Text is empty)");
                    else
                        repDTB.Append(s.Text.Original);
                }
                else
                {
                    repDTB.Append("[Status have not received yet.]");
                }
                TextRenderer.DrawText(e.Graphics,
                    ">> " + repDTB.ToString(), this.Font, ReplyTextRenderer.ClientRectangle,
                    SystemColors.ControlText, TextFormatFlags.EndEllipsis |
                    TextFormatFlags.NoPrefix | TextFormatFlags.SingleLine |
                    TextFormatFlags.VerticalCenter);
            }
        }

        internal void Clear()
        {
            if (curInputMethod != null)
                curInputMethod.Clear();
            if (Core.FormManager.MainForm.PreviousForegroundForm != null &&
                Core.FormManager.MainForm.PreviousForegroundForm.TabControl.SelectedItem != null &&
                Core.FormManager.MainForm.PreviousForegroundForm.TabControl.SelectedItem is XTab.XTabTLItem)
            {
                var svcs = from id in ((XTab.XTabTLItem)Core.FormManager.MainForm.PreviousForegroundForm.TabControl.SelectedItem).TabData.LinkedServiceIds
                           where Core.PluginManager.LookupService(id) != null
                           select Core.PluginManager.LookupService(id);
                this.SelectedServices = svcs.ToArray<Krile.Kernel.Data.Service>();
            }
        }

        internal void ServiceInit()
        {
            List<Service> svcs = new List<Service>(Core.PluginManager.GetServiceEnumerator());
            SelectedServices = svcs.ToArray();
        }

        private void ChangeVLoc_Click(object sender, EventArgs e)
        {
            if (OnChangeVLocRequired != null)
                OnChangeVLocRequired.Invoke(this, e);
        }

        private void CloseButton_Click(object sender, EventArgs e)
        {
            if (OnCloseRequired != null)
                OnCloseRequired.Invoke(this, e);
        }

        public void OnClosing()
        {
            Clear();
            if (Core.Config.Behavior.ClearReplyOnClosePostBox)
                ClearReplyTo();
        }

        private void CancelReply_Click(object sender, EventArgs e)
        {
            ClearReplyTo();
        }

        public event MouseEventHandler OnTitleMouseDown;
        public event MouseEventHandler OnTitleMouseMove;

        private void TitlebarPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (OnTitleMouseDown != null)
                OnTitleMouseDown.Invoke(sender, e);
        }

        private void TitlebarPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (OnTitleMouseMove != null)
                OnTitleMouseMove.Invoke(sender, e);
        }

        public void AppendChild(Form childForm)
        {
            this.ParentForm.AddOwnedForm(childForm);
        }

    }
}
 